package restdom.propertyeditors;

import java.beans.PropertyEditor;
import java.beans.PropertyEditorSupport;
import java.io.Serializable;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.propertyeditors.CustomNumberEditor;
import org.springframework.orm.ObjectRetrievalFailureException;
import org.springframework.util.StringUtils;

import restdom.dao.GenericDao;
import restdom.dao.GenericIdResolver;
import restdom.dao.impl.ReflectiveIdResolver;

public class GenericDaoPropertyEditor<T, ID extends Serializable> extends
		PropertyEditorSupport {

	private GenericDao<T, ID> dao;

	private PropertyEditor idEditor;

	private GenericIdResolver<T, ID> idResolver;

	private boolean createIfNotFound = false;

	private Class<T> entityClass;

	public GenericDaoPropertyEditor(GenericDao<T, ID> dao,
			Class<T> entityClass, Class<ID> idClass) {
		this(dao, entityClass, idClass, false);
	}

	public GenericDaoPropertyEditor(GenericDao<T, ID> dao,
			Class<T> entityClass, Class<ID> idClass, boolean createIfNotFound) {
		this(dao, new ReflectiveIdResolver<T, ID>(entityClass, idClass),
				createIfNotFound);
		this.entityClass = entityClass;
	}

	public GenericDaoPropertyEditor(GenericDao<T, ID> dao,
			Class<T> entityClass, Class<ID> idClass, String propertyName) {
		this(dao, entityClass, idClass, propertyName, false);
	}

	public GenericDaoPropertyEditor(GenericDao<T, ID> dao,
			Class<T> entityClass, Class<ID> idClass, String propertyName,
			boolean createIfNotFound) {
		this(dao, new ReflectiveIdResolver<T, ID>(entityClass, idClass,
				propertyName), createIfNotFound);
		this.entityClass = entityClass;
	}

	public GenericDaoPropertyEditor(GenericDao<T, ID> dao,
			GenericIdResolver<T, ID> idResolver) {
		this(dao, idResolver, false);
	}

	protected GenericDaoPropertyEditor(GenericDao<T, ID> dao,
			GenericIdResolver<T, ID> idResolver, boolean createIfNotFound) {
		this(dao, idResolver, new CustomNumberEditor(Long.class, false),
				createIfNotFound);
	}

	public GenericDaoPropertyEditor(GenericDao<T, ID> dao,
			Class<T> entityClass, Class<ID> idClass, PropertyEditor idEditor) {
		this(dao, entityClass, idClass, idEditor, false);
	}

	public GenericDaoPropertyEditor(GenericDao<T, ID> dao,
			Class<T> entityClass, Class<ID> idClass, PropertyEditor idEditor,
			boolean createIfNotFound) {
		this(dao, new ReflectiveIdResolver<T, ID>(entityClass, idClass),
				idEditor, createIfNotFound);
		this.entityClass = entityClass;
	}

	public GenericDaoPropertyEditor(GenericDao<T, ID> dao,
			Class<T> entityClass, Class<ID> idClass, String propertyName,
			PropertyEditor idEditor) {
		this(dao, entityClass, idClass, propertyName, idEditor, false);
	}

	public GenericDaoPropertyEditor(GenericDao<T, ID> dao,
			Class<T> entityClass, Class<ID> idClass, String propertyName,
			PropertyEditor idEditor, boolean createIfNotFound) {
		this(dao, new ReflectiveIdResolver<T, ID>(entityClass, idClass,
				propertyName), idEditor, createIfNotFound);
		this.entityClass = entityClass;
	}

	public GenericDaoPropertyEditor(GenericDao<T, ID> dao,
			GenericIdResolver<T, ID> idResolver, PropertyEditor idEditor) {
		this(dao, idResolver, idEditor, false);
	}

	protected GenericDaoPropertyEditor(GenericDao<T, ID> dao,
			GenericIdResolver<T, ID> idResolver, PropertyEditor idEditor,
			boolean createIfNotFound) {
		if (dao == null) {
			throw new IllegalArgumentException("Generic DAO must be specified");
		}
		this.dao = dao;
		if (idResolver == null) {
			throw new IllegalArgumentException(
					"Identifier resolver must be specified");
		}
		this.idResolver = idResolver;
		if (idEditor == null) {
			throw new IllegalArgumentException(
					"Identifier property editor must be specified");
		}
		this.idEditor = idEditor;
		this.createIfNotFound = createIfNotFound;
	}

	@Override
	public void setAsText(String text) {
		if (StringUtils.hasText(text)) {
			idEditor.setAsText(text);
			@SuppressWarnings("unchecked")
			ID id = (ID) idEditor.getValue();
			T object = null;
			if (createIfNotFound && entityClass != null) {
				try {
					object = dao.read(id);
				} catch (ObjectRetrievalFailureException e) {
					// Handled locally
				}
				if (object == null) {
					object = entityClass.cast(BeanUtils
							.instantiateClass(entityClass));
					idResolver.setIdValue(object, id);
				}
			} else {
				object = dao.read(id);
			}
			setValue(object);
		}
	}

	@Override
	public String getAsText() {
		@SuppressWarnings("unchecked")
		T object = (T) getValue();
		if (object != null) {
			ID id = idResolver.getIdValue(object);
			idEditor.setValue(id);
			return idEditor.getAsText();
		}
		return null;
	}
}
